import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog
import json
import os

class JsonDatas:
    def __init__(self, root, data=None, filepath=None):
        self.root = root
        self.filepath = filepath
        
        # Set window title based on filepath if provided
        if self.filepath:
            self.root.title(os.path.basename(self.filepath))
        else:
            self.root.title("JSON Editor")
        
        # Initialize Treeview
        self.tree = ttk.Treeview(root, columns=("Key", "Value"), show='headings')
        self.tree.heading("Key", text="Key")
        self.tree.heading("Value", text="Value")
        
        # Bind double-click event to edit cells
        self.tree.bind("<Double-1>", self.edit_cell)
        
        # Add buttons for actions
        button_frame = tk.Frame(root)
        button_frame.pack(fill=tk.X, side=tk.BOTTOM)  # Move the button frame to the bottom
        
        self.add_brother_button = tk.Button(button_frame, text="Add Brother", command=self.add_brother)
        self.add_brother_button.pack(side=tk.LEFT, padx=5)
        
        self.add_son_button = tk.Button(button_frame, text="Add Son", command=self.add_son)
        self.add_son_button.pack(side=tk.LEFT, padx=5)
        
        self.remove_button = tk.Button(button_frame, text="Remove", command=self.remove_element)
        self.remove_button.pack(side=tk.LEFT, padx=5)
        
        self.save_button = tk.Button(button_frame, text="Save", command=self.save_json)
        self.save_button.pack(side=tk.RIGHT, pady=5)
        
        self.data = {}
        self.item_map = {}  # Dictionary to map keys to tree item IDs
        
        if data is not None and isinstance(data, dict):
            self.data = data
            self.populate_tree()  # Populate the tree with initial data
        elif self.filepath:
            self.load_json()
    
    def load_json(self):
        try:
            with open(self.filepath, 'r', encoding='utf-8') as f:
                self.data = json.load(f)
            self.populate_tree()
        except FileNotFoundError:
            messagebox.showerror("Error", f"The file {self.filepath} does not exist.")
        except json.JSONDecodeError:
            messagebox.showerror("Error", f"The file {self.filepath} is not a valid JSON file.")
    
    def save_json(self):
        if self.filepath:
            with open(self.filepath, 'w', encoding='utf-8') as f:
                json.dump(self.data, f, ensure_ascii=False, indent=4)
            messagebox.showinfo("Success", "JSON data saved successfully.")
        else:
            file_path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON files", "*.json")])
            if file_path:
                with open(file_path, 'w', encoding='utf-8') as f:
                    json.dump(self.data, f, ensure_ascii=False, indent=4)
                self.filepath = file_path
                messagebox.showinfo("Success", "JSON data saved successfully.")
    
    def preview(self):
        self.tree.pack(expand=True, fill=tk.BOTH)
    
    def populate_tree(self):
        self.tree.delete(*self.tree.get_children())
        self.item_map.clear()  # Clear the item map before repopulating
        self._populate_tree_recursively("", self.data, "")
    
    def _populate_tree_recursively(self, parent_item_id, data, parent_key, level=0):
        prefix = '--' * level
        for key, value in data.items():
            full_key = f"{parent_key}.{key}" if parent_key else key
            display_key = f"{prefix}{key}"
            item_id = self.tree.insert(parent_item_id, "end", values=(display_key, ""))
            self.item_map[full_key] = item_id
            
            if isinstance(value, dict):
                self._populate_tree_recursively(item_id, value, full_key, level + 1)
            elif isinstance(value, list):
                self._populate_list_recursively(item_id, value, full_key, level + 1)
            else:
                self.tree.set(item_id, "Value", str(value))
    
    def _populate_list_recursively(self, parent_item_id, lst, parent_key, level=0):
        prefix = '--' * level
        for index, value in enumerate(lst):
            key = f"[{index}]"
            full_key = f"{parent_key}.{key}"
            display_key = f"{prefix}[{index}]"
            item_id = self.tree.insert(parent_item_id, "end", values=(display_key, ""))
            self.item_map[full_key] = item_id
            
            if isinstance(value, dict):
                self._populate_tree_recursively(item_id, value, full_key, level + 1)
            elif isinstance(value, list):
                self._populate_list_recursively(item_id, value, full_key, level + 1)
            else:
                self.tree.set(item_id, "Value", str(value))
    
    def edit_cell(self, event):
        region = self.tree.identify_region(event.x, event.y)
        if region == "cell":
            item = self.tree.identify_row(event.y)
            column = self.tree.identify_column(event.x)
            
            if column == "#2":  # Only allow editing the "Value" column
                key = self.tree.item(item, "values")[0]
                parent = self.tree.parent(item)
                
                if parent:
                    parent_data = self.find_parent_data(parent)
                    if key.startswith("["):
                        value = parent_data[int(key.strip("[]"))]
                    else:
                        value = parent_data[self.clean_key(key)]
                else:
                    value = self.data[self.clean_key(key)]
                
                if isinstance(value, (dict, list)):
                    messagebox.showwarning("Warning", "Cannot edit this value as it has children.")
                    return
                
                x, y, width, height = self.tree.bbox(item, column)
                entry_edit = tk.Entry(self.tree, width=width // 7)
                entry_edit.place(x=x, y=y, width=width, height=height)
                entry_edit.insert(0, str(value))
                entry_edit.focus_set()
                
                def on_focus_out(event):
                    new_value = entry_edit.get()
                    entry_edit.destroy()
                    
                    if parent:
                        parent_data = self.find_parent_data(parent)
                        if key.startswith("["):
                            index = int(key.strip("[]"))
                            parent_data[index] = self.convert_value(new_value, parent_data[index])
                        else:
                            parent_data[self.clean_key(key)] = self.convert_value(new_value, parent_data[self.clean_key(key)])
                    else:
                        self.data[self.clean_key(key)] = self.convert_value(new_value, self.data[self.clean_key(key)])
                    
                    self.tree.item(item, values=(key, str(new_value)))
                
                entry_edit.bind("<FocusOut>", on_focus_out)
                entry_edit.bind("<Return>", lambda e: on_focus_out(e))  # Call on_focus_out directly
    
    def find_parent_data(self, parent_item):
        current_item = parent_item
        path = []
        while current_item:
            key = self.tree.item(current_item, "values")[0]
            path.append(self.clean_key(key))
            current_item = self.tree.parent(current_item)
        
        data = self.data
        for key in reversed(path):
            if key.startswith("["):
                index = int(key.strip("[]"))
                data = data[index]
            else:
                data = data[key]
        return data
    
    def clean_key(self, key):
        """Remove prefixes like '--' from the key."""
        return key.lstrip('--')
    
    def convert_value(self, new_value, old_value):
        try:
            if isinstance(old_value, int):
                return int(new_value)
            elif isinstance(old_value, float):
                return float(new_value)
            elif isinstance(old_value, bool):
                return new_value.lower() in ['true', '1', 't']
            else:
                return new_value
        except ValueError:
            messagebox.showerror("Error", "Invalid value entered.")
            return old_value
    
    def add_brother(self):
        selected_item = self.tree.selection()
        if not selected_item:
            messagebox.showwarning("Warning", "No element selected.")
            return
        
        item = selected_item[0]
        parent = self.tree.parent(item)
        key = self.tree.item(item, "values")[0]
        value = ""
        
        if parent:
            parent_data = self.find_parent_data(parent)
            if key.startswith("["):
                index = int(key.strip("[]"))
                parent_data.insert(index + 1, "")
                new_key = f"[{index + 1}]"
                full_key = f"{self.get_full_key(parent)}.{new_key}"
            else:
                new_key = simpledialog.askstring("Input", "Enter new key:")
                if new_key is None or new_key in parent_data:
                    messagebox.showwarning("Warning", "Invalid or duplicate key.")
                    return
                parent_data[new_key] = ""
                full_key = f"{self.get_full_key(parent)}.{new_key}"
        else:
            new_key = simpledialog.askstring("Input", "Enter new key:")
            if new_key is None or new_key in self.data:
                messagebox.showwarning("Warning", "Invalid or duplicate key.")
                return
            self.data[new_key] = ""
            full_key = new_key
        
        self.populate_tree()
        self.select_new_item(full_key)
    
    def add_son(self):
        selected_item = self.tree.selection()
        if not selected_item:
            messagebox.showwarning("Warning", "No element selected.")
            return
        
        item = selected_item[0]
        parent = self.tree.parent(item)
        key = self.tree.item(item, "values")[0]
        
        if parent:
            parent_data = self.find_parent_data(parent)
            if key.startswith("["):
                index = int(key.strip("[]"))
                parent_data = parent_data[index]
                if not isinstance(parent_data, (dict, list)):
                    parent_data = parent_data[index] = {}
                new_key = simpledialog.askstring("Input", "Enter new key:")
                if new_key is None or new_key in parent_data:
                    messagebox.showwarning("Warning", "Invalid or duplicate key.")
                    return
                parent_data[new_key] = ""
                full_key = f"{self.get_full_key(item)}.{new_key}"
            else:
                if not isinstance(parent_data[self.clean_key(key)], (dict, list)):
                    parent_data[self.clean_key(key)] = {}
                new_key = simpledialog.askstring("Input", "Enter new key:")
                if new_key is None or new_key in parent_data[self.clean_key(key)]:
                    messagebox.showwarning("Warning", "Invalid or duplicate key.")
                    return
                parent_data[self.clean_key(key)][new_key] = ""
                full_key = f"{self.get_full_key(item)}.{new_key}"
        else:
            if not isinstance(self.data[self.clean_key(key)], (dict, list)):
                self.data[self.clean_key(key)] = {}
            new_key = simpledialog.askstring("Input", "Enter new key:")
            if new_key is None or new_key in self.data[self.clean_key(key)]:
                messagebox.showwarning("Warning", "Invalid or duplicate key.")
                return
            self.data[self.clean_key(key)][new_key] = ""
            full_key = f"{self.get_full_key(item)}.{new_key}"
        
        self.populate_tree()
        self.select_new_item(full_key)
    
    def get_full_key(self, item):
        path = []
        while item:
            key = self.tree.item(item, "values")[0]
            path.append(self.clean_key(key))
            item = self.tree.parent(item)
        return ".".join(reversed(path))
    
    def select_new_item(self, full_key):
        item_id = self.item_map.get(full_key)
        if item_id:
            self.tree.selection_set(item_id)
            self.tree.focus(item_id)
            self.tree.see(item_id)
    
    def remove_element(self):
        selected_item = self.tree.selection()
        if not selected_item:
            messagebox.showwarning("Warning", "No element selected.")
            return
        
        item = selected_item[0]
        parent = self.tree.parent(item)
        key = self.tree.item(item, "values")[0]
        
        if parent:
            parent_data = self.find_parent_data(parent)
            if key.startswith("["):
                index = int(key.strip("[]"))
                if isinstance(parent_data[index], (dict, list)):
                    confirm = messagebox.askyesno("Confirm", "This element has children. Are you sure you want to delete it?")
                    if not confirm:
                        return
                del parent_data[index]
            else:
                if isinstance(parent_data[self.clean_key(key)], (dict, list)):
                    confirm = messagebox.askyesno("Confirm", "This element has children. Are you sure you want to delete it?")
                    if not confirm:
                        return
                del parent_data[self.clean_key(key)]
        else:
            if isinstance(self.data[self.clean_key(key)], (dict, list)):
                confirm = messagebox.askyesno("Confirm", "This element has children. Are you sure you want to delete it?")
                if not confirm:
                    return
            del self.data[self.clean_key(key)]
        
        self.populate_tree()

if __name__ == "__main__":
    # Create a test JSON data with nested structure (5 levels deep)
    test_data = {
        "level1": {
            "level2": {
                "level3": {
                    "level4": {
                        "level5": "value111"
                    }
                }
            }
        }
    }
    
    # Save the test data to a JSON file for app1
    app1_file_path = "app1.json"
    with open(app1_file_path, 'w', encoding='utf-8') as f:
        json.dump(test_data, f, ensure_ascii=False, indent=4)
    
    # Create two separate windows for testing
    root1 = tk.Tk()
    app1 = JsonDatas(root1, data=test_data, filepath=app1_file_path)
    app1.preview()
    
    root2 = tk.Tk()
    app2 = JsonDatas(root2, filepath="test.json")
    app2.preview()
    
    root1.mainloop()
    root2.mainloop()



